// -*- c-basic-offset: 2 default-tab-width: 2 indent-tabs-mode: t -*-
// vim: autoindent tabstop=2 noexpandtab shiftwidth=2 softtabstop=2
#include "RModule.h"
#include "BuiltIns.h"
#include "bc.h"
#include "Symbol.h"
#include "Promise.h"
#include "Const.h"
#include "Reactor.h"
#include "RCompiler.h"
#include "Closure.h"

using namespace R;

namespace R {
	class RBCModuleTransformer;
}

RModule::RModule(Reactor* vm, uint64_t moduleId) {
	this->vm = vm;
	this->moduleId = moduleId;
	this->entryPoint = NULL;
}

bool RModule::load_file(FILE *fd){
	bc_header header;
	fun_info *finfos;
	int err = read_bc_file(fd, &header, &cpool_orig, &finfos, &cs, malloc);
	if(err < 0) return false;
	codeSize = header.cs;
	// Build a constant pool from an original
  cpool.loadPool(header.pp, cpool_orig);
	loadFunInfos(header.funinfos, finfos, cpool_orig);
#if 1
	pic = new void*[codeSize];
#endif
	return true;
}

void RModule::loadFunInfos(int nb, fun_info *finfos, cpool_entry *cpool_orig){
	nbFuns = nb;
	funInfos = new FunInfo[nb];
	int params;
	while(nb --){
		funInfos[nb].id = nb;
		funInfos[nb].module = this;
		funInfos[nb].start = (finfos + nb)->start;
		funInfos[nb].end = (finfos + nb)->end;
		params = (finfos + nb)->params;
		if(params >= 0)
			funInfos[nb].makeFormals(cpool_orig + params, cpool_orig);
		funInfos[nb].hasClosure = 0;
	}
	free(finfos);
};

static bool findEntryPoint(RModule *module, FunInfo *info) {
	if(!info->parent) {
		module->entryPoint = info;
		return true;
	}
	return false;
}

// Associate for each builtin call it's index in Builtin Map
// FIXME: this make closure information must be part of another pass
static bool builtinPatcher(RModule *module, FunInfo *info) {
	R_VAR(Any*, pe);

	for(int pc = info->start; pc <= info->end; pc++){
		unsigned long bc = module->cs[pc];
		int bid;
		BuiltInMap *bmap;
		int nbParams;
		int nbArgs;
		int child;
		rerror_on(bc > BC_LAST, "Byte code (%lx) code out of sync !", bc);

		switch(bc){
			case BC_CALLSPECIAL:
			case BC_CALLBUILTIN:
				// builtin have a cpool entry idx as param
				// let's replace it by an immediate index
				pe = module->loadFromCode<Any>(pc + 1);
				Assert(pe->getType(pe) == Symbol_ID,
						"BuiltIn CPool entry is not a String (0x%x) but a 0x"XWORD,
						Symbol_ID, pe->getType(pe));

				bid = BuiltInMap::getEntryID(static_cast<Symbol*>(pe)->text);
				if(!BuiltInMap::isValidEntry(bid))
					warning("Unimplemented builtin '%s'", static_cast<Symbol*>(pe)->text);

				bmap = BuiltInMap::getEntry(bid);
				nbParams = bmap->nbParams;
				nbArgs = module->cs[pc + 2 + (bc==BC_CALLBUILTIN)]; // FIXME remove this comparisons when we'll fixe the bcode for CALLSPECIAL (3 args like CALLBUILTIN)

				rerror_on(bmap->isSpecial != (bc == BC_CALLSPECIAL),
					"%s is used to call '%s' instead of %s",
					bmap->isSpecial ? "CALLBUILTIN" : "CALLSPECIAL",
					static_cast<Symbol*>(pe)->text,
					bmap->isSpecial ? "CALLSPECIAL" : "CALLBUILTIN");

				rerror_on(!( nbArgs == nbParams	||  nbArgs > (- nbParams - 1)),
						"Wrong number of arguments in call to '%s', %d given where %d where expected",
						static_cast<Symbol*>(pe)->text,
						nbArgs, nbParams);

				module->cs[pc + 1] = bid;
				break;
			case BC_CALL:
				module->pic[pc + 3] = NULL;
				module->pic[pc + 2] = (int32_t*)malloc(sizeof(int32_t)*(module->cs[pc + 3] + 1)); // The + 1 is to store the size of the ``...''
				break;
			case BC_MKCLOSURE:
				info->hasClosure++; // This hasClosure is stupid since Promise need also to keep alive env ...
			case BC_MKPROM:
				child = module->cs[pc + 1];
				rerror_on(module->funInfos[child].parent, "this function has more than one parent");
				module->funInfos[child].parent = info;
				break;
		}
		pc += BC_size[bc]; // Skip params
	}

	if(info->isClosure())
	// Fix defaults arguments to function
		for(int64_t i = 0; i < info->bindings->size; i++) {
			static_cast<Promise*>(info->bindings->get(i))->fun->parent = info;
		}
	return false;
}

#ifdef R_FAST_LOOKUP
static void dumpSet(CVector<Symbol*> *ws) {
	for(int i = 0; i < ws->size(); i++){
		printf("%d:", i);
		ws->get(i)->print();
		printf("\n");
	}
}

static int addInSet(Symbol *sym, CVector<Symbol*> *set) {
	R_PARAM(sym);
	int pos;
	if((pos = set->has(sym)) == -1) {
		pos = set->size();
		set->push(sym);
	}
	return pos;
}

void ReadSet::add(Symbol *sym, FunInfo *info) {
	R_PARAM(sym); 

	int hops = 1;
	int pos;
	FunInfo* current = info->parent;

	while(current != NULL) {
		if((pos = current->writeSet.has(sym))>= 0) {
			ReadSetEntry rse(sym, hops, pos);
			push(rse);
			return;
		}
		hops ++;
		current = current->parent;
	}

	ReadSetEntry rse(sym, 0, 0);
	push(rse);
}

static void buildReadSet(FunInfo *info, CVector<Symbol*> *set) {
	R_VAR(Symbol *, sym);

	ReadSet *readSet = &info->readSet;
	CVector<Symbol*> *ws = &info->writeSet;

	for(int i = 0; i < ws->size(); i ++) {
		sym = ws->get(i);
		readSet->add(sym, info);
		ReadSet::remove(sym, set);
	}

	for(int i = 0; i < set->size(); i++) {
		readSet->add(set->get(i), info);
	}
}

static bool buildReadWriteSet(RModule *module, FunInfo *info) {
	R_VAR(Symbol*, sym);

	//It is assumed for now, that function are processed in a topological order

	CVector<Symbol*> rs;
	CVector<Symbol*> *ws = &info->writeSet;

	if(info->isClosure()) {
		Bindings *bindings = info->bindings;
		for(int i = 0; i < bindings->size ; i++) {
			sym = bindings->key(i);
			rerror_on(ws->has(sym) != -1, "Duplicate names in parameters: %s", sym->text);
			ws->push(sym);
		}
	} else if(info->parent)
		ws = &info->parent->writeSet;

	for(int pc = info->start; pc <= info->end; pc++) {
		unsigned long bc = module->cs[pc];
		rerror_on(bc > BC_LAST, "Byte code (%lx) code out of sync !", bc);

		sym = NULL;
		switch(bc){
			case BC_SETVAR:
			case BC_DFLTSUBASSIGN:
			case BC_DFLTSUBASSIGN2:
				module->pic[pc] = (void*)addInSet(module->loadFromCode<Symbol>(pc + 1), ws);
				break;
			case BC_STARTFOR:
				module->pic[pc] = (void*)addInSet(module->loadFromCode<Symbol>(pc + 2), ws);
				break;

			case BC_GETVAR:
			case BC_GETVAR_MISSOK:
			case BC_CALL:
			case BC_SETVAR2:
				addInSet(module->loadFromCode<Symbol>(pc + 1), &rs);
				break;
		}
		pc += BC_size[bc]; // Skip params
	}

	buildReadSet(info->isClosure() || !info->parent ? info : info->parent, &rs);
	//dumpSet(&rs);
	//dumpSet(ws);
	return false;
}

static bool patchLookupInstructions(RModule *mod, FunInfo *fun) {
	R_VAR(Symbol*, sym);
	ReadSet *rs = &fun->readSet;
	CVector<Symbol*> *ws = &fun->writeSet;
	if(fun->parent && !fun->isClosure()) {
		rs = &fun->parent->readSet;
		ws = &fun->parent->writeSet;
	}

	for(int pc = fun->start; pc <= fun->end; pc++) {
		unsigned long bc = mod->cs[pc];
		int pos;
		ReadSetEntry *rse;

		switch(bc){
			case BC_GETVAR:
			case BC_GETVAR_MISSOK:
			case BC_CALL:
				sym = mod->loadFromCode<Symbol>(pc + 1);
				pos = ws->has(sym);
				rse = rs->lookup(sym);
				mod->pic[pc] = (void*)pos; // This is a local var
				if(rse) 
					mod->pic[pc + 1] = (void*)(rse->hops << sizeof(rse->hops) | rse->position);
				break;
			case BC_SETVAR2:
				sym = mod->loadFromCode<Symbol>(pc + 1);
				rse = rs->lookup(sym);
				if(rse) {
					mod->pic[pc] = (void*)rse->position;
					mod->pic[pc + 1] = (void*)rse->hops;
				}
				break;
		}
		pc += BC_size[bc]; // Skip params
	}
	return false;
}
#endif

typedef
	bool (*RBCFunTransformer)(RModule *, FunInfo *);

static RBCFunTransformer defaultTransformerPasses[] = {
	builtinPatcher // This pass must be first since it change some bcodes according to bc.def
	, findEntryPoint // This pass must be done after builtinPatcher which set parents
#ifdef R_FAST_LOOKUP
	, buildReadWriteSet
	, patchLookupInstructions
#endif
	//, stackValidator // We have to build this with a static pass of llvm
	//, getBranchLabels
};

class R::RBCModuleTransformer {
	RBCFunTransformer *transformers;
	int nbPasses;

	static RBCModuleTransformer *defaultAnalysis;

	public:

	RBCModuleTransformer(RBCFunTransformer passes[], int nb){
		transformers = passes;
		nbPasses = nb;
	}

	static RBCModuleTransformer *getDefaultModuleTransformer(){
		if(!defaultAnalysis)
			defaultAnalysis = new RBCModuleTransformer(defaultTransformerPasses, sizeof(defaultTransformerPasses)/sizeof(*defaultTransformerPasses));
		return defaultAnalysis;
	}

	void transform(RModule *module) {
		int nbFuns = module->nbFuns;
		for(int pass = 0; pass < nbPasses; pass++)
			for(int fun = 0 ; fun < nbFuns; fun++)
				if((*transformers[pass])(module, module->funInfos + fun))
					break;
	}
};

void RModule::load(const char* fileName) {
  FILE* fd;

	this->fileName = File::resolve(fileName);
  rprintf("opening: %s", fileName);

	fd = File::open(fileName, "r");
	rerror_on(!fd, "Unable to open '%s'", fileName);

  rerror_on(!load_file(fd), "Corrupt bytecode file '%s'", fileName);
	rerror_on(!funInfos, "no fun info loaded");

	RBCModuleTransformer::getDefaultModuleTransformer()->transform(this);
}

RBCModuleTransformer *RBCModuleTransformer::defaultAnalysis;
